﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Threading.Tasks;

using Uber.Utilities;
using Uber.Collections;
using Uber.HabboHotel.Items;
using Uber.HabboHotel.GameClients;
using Uber.Storage;
using Uber.Core;

namespace Uber.HabboHotel.Rooms
{
    class RoomManager
    {
        public readonly int MAX_PETS_PER_ROOM = 5;

        private SafeList<Room> Rooms;

        private Dictionary<string, RoomModel> Models;

        private SafeList<TeleUserData> TeleActions;

        private Task EngineThread;

        private SafeList<uint> RoomsToUnload;

        public int LoadedRoomsCount
        {
            get
            {
                return this.Rooms.Count;
            }
        }

        public RoomManager()
        {
            this.Rooms = new SafeList<Room>();
            this.Models = new Dictionary<string, RoomModel>();
            this.TeleActions = new SafeList<TeleUserData>();

            this.EngineThread = new Task(ProcessEngine, System.Threading.CancellationToken.None, TaskCreationOptions.LongRunning);

            this.RoomsToUnload = new SafeList<uint>();
        }

        public void StartRoomEngine()
        {
            this.EngineThread.Start();
        }

        public void StopRoomEngine()
        {
            this.EngineThread.Wait();
            this.EngineThread.Dispose();
            this.EngineThread = null;

            UnloadAllRooms(true);
        }

        public void AddTeleAction(TeleUserData Act)
        {
            this.TeleActions.Add(Act);
        }

        public List<Room> GetRoomsForNavigator()
        {
            var NavigatorRooms = new List<Room>();

            foreach (Room Room in Rooms)
            {
                if (Room.UsersNow > 0 && !Room.IsPublic && Room != null)
                {
                    NavigatorRooms.Add(Room);
                }
            }

            var SortedRooms = from rm in NavigatorRooms
                              orderby rm.UsersNow descending
                              select rm;

            return SortedRooms.ToList();
        }

        public List<Room> GetEventRoomsForCategory(int Category)
        {
            var EventRooms = new List<Room>();

            foreach (Room Room in Rooms)
            {
                if (Room.Event == null)
                {
                    continue;
                }

                if (Category > 0 && Room.Event.Category != Category)
                {
                    continue;
                }

                EventRooms.Add(Room);
            }

            return EventRooms.ToList();
        }

        internal void ProcessEngine()
        {
            while (this.EngineThread != null)
            {
                DateTime ExecutionStart = DateTime.Now;

                for(int i = 0; i < Rooms.Count; i++)
                {
                    Room Room = Rooms[i]; if (Room == null) { continue; }
                    Room.ProcessRoom();
                }

                for (int i = 0; i < RoomsToUnload.Count; i++)
                {
                    uint Room = RoomsToUnload[i]; if (Room <= 0) { continue; }
                    UnloadRoom(Room);
                }

                this.RoomsToUnload = null;
                this.RoomsToUnload = new SafeList<uint>();

                for (int i = 0; i > TeleActions.Count; i++)
                {
                    TeleUserData Tele = TeleActions[i]; if (Tele == null || !TeleHandler.IsTeleLinked(Tele.TeleId)) { continue; }

                    Tele.Execute();
                }

                this.TeleActions = null;
                this.TeleActions = new SafeList<TeleUserData>();

                double sleepTime = 500 - (DateTime.Now - ExecutionStart).TotalMilliseconds;

                if (sleepTime < 0)
                {
                    sleepTime = 0;
                }

                if (sleepTime > 500)
                {
                    sleepTime = 500;
                }

                this.EngineThread.Wait(Convert.ToInt32(Math.Floor(sleepTime)));
            }
        }

        internal void UnloadAllRooms(bool Destroy)
        {
            for (int i = 0; i < Rooms.Count; i++)
            {
                Room Room = Rooms[i]; if (Room == null) { continue; }

                UnloadRoom(Room.RoomId);
            }

            Rooms = null;
            if (!Destroy) { Rooms = new SafeList<Room>(); }
        }

        public void LoadModels()
        {
            this.Models = null;
            this.Models = new Dictionary<string, RoomModel>();

            DataTable Data = null;

            using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
            {
                Data = dbClient.ReadDataTable("SELECT * FROM room_models");
            }

            if (Data == null)
            {
                return;
            }

            foreach (DataRow Row in Data.Rows)
            {
                Models.Add((string)Row["id"], new RoomModel((string)Row["id"], (int)Row["door_x"],
                    (int)Row["door_y"], (Double)Row["door_z"], (int)Row["door_dir"], (string)Row["heightmap"],
                    (string)Row["public_items"], UberEnvironment.EnumToBool(Row["club_only"].ToString())));
            }
        }

        public RoomModel GetModel(string Model)
        {
            if (Models.ContainsKey(Model))
            {
                return Models[Model];
            }

            return null;
        }

        public RoomData GenerateNullableRoomData(uint RoomId)
        {
            if (GenerateRoomData(RoomId) != null)
            {
                return GenerateRoomData(RoomId);
            }

            RoomData Data = new RoomData();
            Data.FillNull(RoomId);
            return Data;
        }

        public RoomData GenerateRoomData(uint RoomId)
        {
            RoomData Data = new RoomData();

            if (IsRoomLoaded(RoomId))
            {
                Data.Fill(GetRoom(RoomId));
            }
            else
            {
                DataRow Row = null;

                using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
                {
                    Row = dbClient.ReadDataRow("SELECT * FROM rooms WHERE id = '" + RoomId + "' LIMIT 1");
                }

                if (Row == null)
                {
                    return null;
                }

                Data.Fill(Row);
            }

            return Data;
        }

        public Boolean IsRoomLoaded(uint RoomId)
        {
            if (GetRoom(RoomId) == null)
            {
                return false;
            }

            return true;
        }

        public void LoadRoom(uint Id)
        {
            if (IsRoomLoaded(Id))
            {
                return;
            }

            RoomData Data = GenerateRoomData(Id);

            if (Data == null)
            {
                return;
            }

            Rooms.Add(new Room(Data.Id, Data.Name, Data.Description, Data.Type, Data.Owner, Data.Category, Data.State,
                Data.UsersMax, Data.ModelName, Data.CCTs, Data.Score, Data.Tags, Data.AllowPets, Data.AllowPetsEating,
                Data.AllowWalkthrough, Data.Hidewall, Data.Icon, Data.Password, Data.Wallpaper, Data.Floor, Data.Landscape));
        }

        public void RequestRoomUnload(uint Id)
        {
            if (!IsRoomLoaded(Id))
            {
                return;
            }

            RoomsToUnload.Add(Id);
        }

        public void UnloadRoom(uint Id)
        {
            Room Room = GetRoom(Id);

            if (Room == null)
            {
                return;
            }

            Room.Destroy();
            Rooms.Remove(GetRoom(Id));
        }

        public uint GetRandomRoomIdWithUser(GameClient Session)
        {
            var SpecialRooms = new List<uint>(); // kiss my ash PEJump

            uint RoomId = 0;

            foreach (Room Room in Rooms)
            {
                if (Room == null || !Room.KeepAlive)
                {
                    continue;
                }

                if (Room.RoomId == Session.GetHabbo().CurrentRoomId)
                {
                    continue;
                }

                if (Room.UserCount > 0 && Room.Password == "" && !Room.IsPublic)
                {
                    SpecialRooms.Add(Room.RoomId);
                }
            }

            if (SpecialRooms.Count > 0)
            {
                Random Random = new Random();
                int i = Random.Next(0, SpecialRooms.Count);

                RoomId = SpecialRooms[i];
            }

            return RoomId;
        }

        public Room GetRoom(uint RoomId)
        {
            for (int i = 0; i < Rooms.Count; i++)
            {
                Room Room = Rooms[i]; if (Room == null) { continue; }

                if (Room.RoomId == RoomId) { return Room; }                 
            }

            return null;
        }

        public RoomData CreateRoom(GameClient Session, string Name, string Model)
        {
            Name = UberEnvironment.FilterInjectionChars(Name);

            if (!Models.ContainsKey(Model))
            {
                Session.SendNotif("Sorry, this room model has not been added yet. Try again later.");
                return null;
            }

            if (Models[Model].StaticFurniMap != "") // Hack attempt
            {
                return null;
            }

            if (Models[Model].ClubOnly && !Session.GetHabbo().HasFuse("fuse_use_special_room_layouts"))
            {
                Session.SendNotif("You must be an Club member to use that room layout.");
                return null;
            }

            if (Name.Length < 3 || Name == "")
            {
                Session.SendNotif("Room name is too short for room creation!");
                return null;
            }

            if (Name.ToLower() == "www.holoscripter.ya.st")
            {
                return null;
            }

            using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
            {
                dbClient.AddParamWithValue("caption", Name);
                dbClient.AddParamWithValue("model", Model);
                dbClient.AddParamWithValue("username", Session.GetHabbo().Username);
                dbClient.ExecuteQuery("INSERT INTO rooms (roomtype,caption,owner,model_name) VALUES ('private',@caption,@username,@model)");
            }

            uint RoomId = 0;

            using (DatabaseClient dbClient = UberEnvironment.GetDatabase().GetClient())
            {
                dbClient.AddParamWithValue("caption", Name);
                dbClient.AddParamWithValue("username", Session.GetHabbo().Username);
                RoomId = (uint)dbClient.ReadDataRow("SELECT id FROM rooms WHERE owner = @username AND caption = @caption ORDER BY id DESC")[0];
            }

            return GenerateRoomData(RoomId);
        }
    }

    class TeleUserData
    {
        private RoomUser User;
        private uint RoomId;
        public uint TeleId;

        public TeleUserData(RoomUser mUser, uint mRoomId, uint mTeleId)
        {
            this.User = mUser;
            this.RoomId = mRoomId;
            this.TeleId = mTeleId;
        }

        public void Execute()
        {
            if (User == null || User.IsBot || User.GetClient() == null || User.GetClient().GetHabbo() == null)
            {
                return;
            }

            User.GetClient().GetHabbo().IsTeleporting = true;
            User.GetClient().GetHabbo().TeleporterId = TeleId;
            User.GetClient().GetMessageHandler().PrepareRoomForUser(RoomId, "");

            //User.GetClient().GetHabbo().UserIsTryingToTele = false;
        }
    }
}
